1 module hip.hiprenderer.backend.metal.mtltexture;
2 version(AppleOS):
3 import metal;
4 import hip.hiprenderer;
5 import hip.hiprenderer.backend.metal.mtlrenderer;
6 import hip.console.log;
7 import metal.texture;
8 
9 
10 MTLPixelFormat getPixelFormat(in IImage img)
11 {
12     final switch(img.getBytesPerPixel)
13     {
14         case 1: return MTLPixelFormat.R8Unorm;
15         case 4: return MTLPixelFormat.RGBA8Unorm;
16         case 2:
17         case 3:
18     }
19     assert(0);
20 }
21 
22 
23 MTLSamplerAddressMode fromHipTextureWrapMode(TextureWrapMode m)
24 {
25     final switch(m)
26     {
27         case TextureWrapMode.CLAMP_TO_EDGE:
28             return MTLSamplerAddressMode.ClampToEdge;
29         case TextureWrapMode.MIRRORED_CLAMP_TO_EDGE:
30             return MTLSamplerAddressMode.MirrorClampToEdge;
31         case TextureWrapMode.REPEAT:
32             return MTLSamplerAddressMode.Repeat;
33         case TextureWrapMode.MIRRORED_REPEAT:
34             return MTLSamplerAddressMode.MirrorRepeat;
35         case TextureWrapMode.CLAMP_TO_BORDER:
36             return MTLSamplerAddressMode.ClampToBorderColor;
37         case TextureWrapMode.UNKNOWN: assert(false, "Don't use that");
38 
39     }
40 }
41 
42 class HipMTLTexture : IHipTexture
43 {
44     MTLTextureDescriptor desc;
45     MTLTexture texture;
46     MTLDevice device;
47     MTLSamplerState sampler;
48     MTLSamplerDescriptor samplerDesc = null;
49 
50     HipMTLRenderer mtlRenderer;
51     MTLCommandQueue cmdQueue;
52 
53     uint width, height;
54 
55     IHipTexture getBackendHandle(){return this;}
56 
57     this(MTLDevice device, MTLCommandQueue cmdQueue, HipMTLRenderer mtlRenderer)
58     {
59         this.device = device;
60         this.cmdQueue = cmdQueue;
61         this.mtlRenderer = mtlRenderer;
62         samplerDesc = MTLSamplerDescriptor.alloc.initialize;
63 
64         setWrapMode(TextureWrapMode.REPEAT);
65         setTextureFilter(TextureFilter.NEAREST, TextureFilter.NEAREST);
66     }
67 
68     void setWrapMode(TextureWrapMode mode)
69     {
70         MTLSamplerAddressMode wrap = mode.fromHipTextureWrapMode;
71         samplerDesc.rAddressMode = wrap;
72         samplerDesc.sAddressMode = wrap;
73         samplerDesc.tAddressMode = wrap;
74         if(sampler) sampler.release();
75         sampler = device.newSamplerStateWithDescriptor(samplerDesc);
76     }
77 
78     void setTextureFilter(TextureFilter min, TextureFilter mag)
79     {
80         final switch ( min ) with(TextureFilter)
81         {
82             case LINEAR:
83                 samplerDesc.minFilter = MTLSamplerMinMagFilter.Linear;
84                 samplerDesc.mipFilter = MTLSamplerMipFilter.NotMipmapped;
85                 break;
86             case NEAREST:
87                 samplerDesc.minFilter = MTLSamplerMinMagFilter.Nearest;
88                 samplerDesc.mipFilter = MTLSamplerMipFilter.NotMipmapped;
89                 break;
90             case NEAREST_MIPMAP_NEAREST:
91                 samplerDesc.minFilter = MTLSamplerMinMagFilter.Nearest;
92                 samplerDesc.mipFilter = MTLSamplerMipFilter.Nearest;
93                 break;
94             case LINEAR_MIPMAP_NEAREST:
95                 samplerDesc.minFilter = MTLSamplerMinMagFilter.Linear;
96                 samplerDesc.mipFilter = MTLSamplerMipFilter.Nearest;
97                 break;
98             case NEAREST_MIPMAP_LINEAR:
99                 samplerDesc.minFilter = MTLSamplerMinMagFilter.Nearest;
100                 samplerDesc.mipFilter = MTLSamplerMipFilter.Linear;
101                 break;
102             case LINEAR_MIPMAP_LINEAR:
103                 samplerDesc.minFilter = MTLSamplerMinMagFilter.Linear;
104                 samplerDesc.mipFilter = MTLSamplerMipFilter.Linear;
105                 break;
106         }
107         final switch ( mag ) with(TextureFilter)
108         {
109             case LINEAR:
110                 samplerDesc.magFilter = MTLSamplerMinMagFilter.Linear;
111                 samplerDesc.mipFilter = MTLSamplerMipFilter.NotMipmapped;
112                 break;
113             case NEAREST:
114                 samplerDesc.magFilter = MTLSamplerMinMagFilter.Nearest;
115                 samplerDesc.mipFilter = MTLSamplerMipFilter.NotMipmapped;
116                 break;
117             case NEAREST_MIPMAP_NEAREST:
118                 samplerDesc.magFilter = MTLSamplerMinMagFilter.Nearest;
119                 samplerDesc.mipFilter = MTLSamplerMipFilter.Nearest;
120                 break;
121             case LINEAR_MIPMAP_NEAREST:
122                 samplerDesc.magFilter = MTLSamplerMinMagFilter.Linear;
123                 samplerDesc.mipFilter = MTLSamplerMipFilter.Nearest;
124                 break;
125             case NEAREST_MIPMAP_LINEAR:
126                 samplerDesc.magFilter = MTLSamplerMinMagFilter.Nearest;
127                 samplerDesc.mipFilter = MTLSamplerMipFilter.Linear;
128                 break;
129             case LINEAR_MIPMAP_LINEAR:
130                 samplerDesc.magFilter = MTLSamplerMinMagFilter.Linear;
131                 samplerDesc.mipFilter = MTLSamplerMipFilter.Linear;
132                 break;
133         }
134         if(sampler) sampler.release();
135         sampler = device.newSamplerStateWithDescriptor(samplerDesc);
136     }
137 
138     private static bool textureToSquare(out ubyte[] ret, const ubyte[] textureData, uint width, uint height)
139     {
140         uint nWidth = width;
141         if(nWidth < height) nWidth = height;
142         else return false;
143         ret = new ubyte[nWidth*height];
144         
145         for(size_t i = 0; i < height; i++)
146             ret[i*nWidth..i*nWidth+width] = textureData[i*width..(i+1)*width];
147         return true;
148     }
149 
150     protected bool loadImpl(in IImage img)
151     {
152         desc = MTLTextureDescriptor.alloc.initialize;
153         desc.pixelFormat = getPixelFormat(img);
154         desc.width = img.getWidth();
155         desc.height = img.getHeight();
156         desc.textureType = MTLTextureType._2D;
157         desc.storageMode = MTLStorageMode.Private;
158 
159         if(desc is null)
160             return false;
161         
162         width = img.getWidth;
163         height = img.getHeight;
164 
165         const ubyte[] data = img.getPixels;
166         ubyte[] squareData; 
167 
168         MTLCommandBuffer b = cmdQueue.defaultCommandBuffer();
169         MTLBlitCommandEncoder blit = b.blitCommandEncoder();
170         MTLBuffer imageBuffer;
171         NSUInteger bytesPerRow;
172         NSUInteger bytesPerImage;
173 
174         texture = device.newTextureWithDescriptor(desc);
175 
176         if(textureToSquare(squareData, data, img.getWidth, img.getHeight))
177         {
178             assert(img.getHeight > img.getWidth);
179             imageBuffer = device.newBuffer(squareData.ptr, squareData.length, MTLResourceOptions.StorageModeShared);
180             bytesPerRow = img.getHeight * img.getBytesPerPixel;
181             bytesPerImage = squareData.length;
182         }
183         else
184         {
185             imageBuffer = device.newBuffer(img.getPixels.ptr, img.getPixels.length, MTLResourceOptions.StorageModeShared);
186             bytesPerRow = img.getWidth * img.getBytesPerPixel;
187             bytesPerImage = img.getPixels.length;
188         }
189         blit.copyFromBuffer(
190             imageBuffer, 0, bytesPerRow, bytesPerImage,
191             MTLSize(desc.width, desc.height, 1),
192             texture, 0, 0, MTLOrigin(0,0,0)
193         );
194         blit.optimizeContentsForGPUAccess(texture);
195         blit.endEncoding();
196         b.commit();
197         b.waitUntilCompleted();
198         if(b.error)
199             NSLog("Command Buffer Error: %@".ns, b.error);
200         // imageBuffer.dealloc();
201         if(squareData.ptr !is null)
202         {
203             import core.memory;
204             GC.free(squareData.ptr);
205         }
206 
207         return texture !is null;
208     }
209 
210     void bind(int slot = 0)
211     {
212         mtlRenderer.getEncoder.setFragmentSamplerState(sampler, slot);
213         mtlRenderer.getEncoder.setFragmentTexture(texture, slot);
214     }
215 
216     void unbind(int slot = 0)
217     {
218         mtlRenderer.getEncoder.setFragmentSamplerState(null, slot);
219         mtlRenderer.getEncoder.setFragmentTexture(null, slot);
220     }
221     bool hasSuccessfullyLoaded(){return width != 0;}
222     int getWidth() const{return width;}
223     int getHeight() const{return height;}
224 }